
#ifdef a
//对象模型   一般通过内存查看，而不是监视（监视无顺序）
/*
	1.同虚拟继承类似，前4个字节指针
	2.指针指向的是虚表/虚函数表（一个连续的空间），内部存储的是虚函数的地址
	3.虚表构建过程：按照类中虚函数的声明顺序依次添加
	4.一个类的多个对象共享同一个虚表
*/
 

//虚表的构建过程

//基类对象和子类对象不会共享同一张虚表,但子类和基类虚表不同，内容相同。

//子类重写后，子类虚表中对应的虚函数地址完成替换


//虚表最后以0结尾（vs）
// 
//子类虚表的构建过程

#include<iostream>
using namespace std;
class A {
public:
	A(int a)
		:_a(a)
	{}

	virtual void Fun1() {
		cout << "A::Fun1" << endl;
	}

	virtual void Fun2() {
		cout << "A::Fun2" << endl;
	}

	void Fun3() {
		cout << "A::Fun3" << endl;
	}

	int _a;
};

class B :public A {
public:
	B(int a, int b)
		:A(a)
		,_b(b)
	{}

	int _b;
};
int main() {
	cout << sizeof(A) << endl;
	cout << sizeof(B) << endl;
	
	A a1(1);
	A a2(1);

	B b(1, 2);

/*
//若函数均为非虚函数，则结果应为4 和 8
//当函数Fun1，Fun2为虚函数而Fun3不变时，结果为8 12，均增加4个字节长度
//当函数均为虚函数时。结果仍为 8 12，增加4个字节长度

		从内存中可发现增加的4个字节为基类对象的前4个字节，且与基类对象地址的前4个字节相同，即编译器调用默认构造函数对基类对象前4个字节
	赋值为基类对象地址的前4个字节，查看这4个地址所指向空间，发现指向一张虚表，虚表内存放着基类虚函数的地址，其顺序按照虚函数在类中的声明
	顺序。

	    构建第二个基类对象发现两者所指向的空间为同一张虚表，即同一类的不同对象共同使用同一张虚表

		B类公有继承A类，现在构造B类对象，A类的虚函数B类对象会继承，通过监视及内存发现，B类对象中4个字节为B类所构建对象地址的前4个字节，
	其虚指针指向的虚表地址与A类对象不同，但虚表内虚函数相对虚表起始位置的偏移量，虚函数顺序和其指向的虚函数地址都与其基类相同

*/
	return 0;
}
#endif

#ifdef a
#include<iostream>
using namespace std;
typedef void(*T)();//将void(*)()类型重命名为T
class A {
public:
	A(int a)
		:_a(a)
	{}

	virtual void Fun1() {
		cout << "A::Fun1" << endl;
	}

	virtual void Fun2() {
		cout << "A::Fun2" << endl;
	}

	void Fun3() {
		cout << "A::Fun3" << endl;
	}

	int _a;
};

class B :public A {
public:
	B(int a, int b)
		:A(a)
		, _b(b)
	{}
	
	//对Fun1进行重写
	void Fun1() {
		cout << "B::Fun1" << endl;
	}

	//对子类新增虚函数
	virtual void Fun4() {
		cout << "B::Fun4" << endl;
	}

	int _b;
};

//打印虚函数地址查看
void Print(A& a) {
	//需要拿到虚表的地址，对内部的内容进行循环打印
		//虚表的地址是对象内容的前4个字节，要访问对象内容，就需要有对象的地址，对其解引用后取前4个字节
		//即 *(int*)&a,若直接*&a，得到的还是对象a本身，而a本身有8个字节，因此需要强转为int*后解引用，这样访问得到的只有前4个字节
		//*(int*)&a 得到的值为int类型，其值本身和虚表地址值相等，要将该int值当成地址，以指针形式对虚表进行访问，并为了方便之后的++等操作
		//需要把该int值转化为类型为虚表内部元素类型的指针，虚表作为一段连续存储的空间，就相当于一个数组，内部存储元素为函数指针，则虚表
		//其实就是一个函数指针数组，如此就需要将int类型的值转化为数组内部函数指针的类型，就如同int*访问int数组，char*访问char数组
		//函数指针就是指向虚函数的指针，由于虚函数类型返回值均为void，参数均为空，则该指针为	void(*p)(),p为指针变量，使用typedef将该类型简化为T
		//则虚指针为 (T*)*(int*)&a,同时该指针也是第一个虚函数的地址。
		
	T* p = (T*) * (int*)&a;
	//根据内存查看虚表中最后一个虚函数后面的空间内4个字节全是0（vs），即可以此为条件进行遍历循环调用函数
	while (*p) {
		(*p)();//调用函数（*p本身就是该地址对应的虚函数）
		p++;
	}
		

}

int main() {
	cout << sizeof(A) << endl;
	cout << sizeof(B) << endl;

	A a(1);
	B b(2, 3);
	Print(a);
	Print(b);
	/*
		若在子类对基类的虚函数进行重写，则虚表情况如何？
			重写后，发现基类虚表无变化，而子类虚表中进行重写过的虚函数地址被覆盖，不再与基类相同，顺序不变



		若在子类新增虚函数，则子类虚表情况如何？
			在子类中新增虚函数Fun4,发现监视窗口无变化，但实际上b对象的虚表中新增了该虚函数的地址，
			顺序按照子类中虚函数声明的顺序，而新增虚函数地址都在基类继承的虚函数以下

	*/
	return 0;
}

#endif